{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Darden API Access"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To access the Darden API, two separate security methods must be negotiated. We shall address them in order and separately, combining the solution in the end.\n",
    "\n",
    "\n",
    "\n",
    "1. ### Client Certificate\n",
    "2. ### XMLSec Payload Encryption\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Client Setup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "from requests import Session\n",
    "from requests_pkcs12 import Pkcs12Adapter\n",
    "from lxml import etree\n",
    "import xmlsec\n",
    "import base64\n",
    "import json"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Client Certificate"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Client certificate files are packed as .p12 files inside the apk or ios bundles. Usually it has \"clientcert\" in the name. Passwords can be found in the bundle as well, usually hardcoded into the file or set in a .plist file somewhere. Here I've found the right client cert and pass ahead of time obviously."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "clientcert = \"olivegardenclientcert_feb2021.p12\"\n",
    "certpass = \"fHf%,3t7m6ZFr9#\"\n",
    "session = Session()\n",
    "session.mount('https://services.darden.com/api', \n",
    "              Pkcs12Adapter(pkcs12_filename=clientcert,\n",
    "                pkcs12_password=certpass))\n",
    "session.headers.update(\n",
    "                    {'User-Agent': 'okhttp/3.10.0', 'Content-Type': 'application/xml'})\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## XMLSec Payload Encryption"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "XMLSec is a standardized way to encrypt XML payloads. It's not very intuitive now, but it's been around for a while so it's still used in a lot of places."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Load Template and Encryption Keys"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since it's standardized, a basic template covers all use cases. It is saved externally as template.xml. Encryption keys are also loaded. Note that encryption keys should be pem format as it only needs to be used one way and does not require a private key. Decryption keys do require a private key and are in .p12 format."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [],
   "source": [
    "pubkeyfile = \"msgenc_cert.pem\"\n",
    "\n",
    "root = etree.parse('xmltemplate.xml').getroot()\n",
    "manager = xmlsec.KeysManager()\n",
    "manager.add_key(xmlsec.Key.from_file(pubkeyfile, format=xmlsec.constants.KeyDataFormatCertPem))\n",
    "ctx = xmlsec.EncryptionContext(manager)\n",
    "ctx.key = xmlsec.Key.generate(xmlsec.constants.KeyDataAes, 128, xmlsec.constants.KeyDataTypeSession)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create login payload"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The login payload is relatively straightforward, but takes some effort to capture. Also included: sample credentials"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [],
   "source": [
    "email = \"stephen.champion27@gmail.com\"\n",
    "password = \"Libby2010\"\n",
    "\n",
    "fsz = '{{\\n  \"password\" : \"{password}\",\\n  \"emailId\" : \"{email}\"\\n}}'.format(password=password, email=email)\n",
    "payload = \"<message>\" + base64.urlsafe_b64encode(bytes(fsz, 'utf-8')).decode('utf-8') + \"</message>\"\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Encrypt payload"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [],
   "source": [
    "encrypted = ctx.encrypt_xml(root, etree.fromstring(payload))\n",
    "xmlfile = etree.tostring(encrypted)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Send payload"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You should take note that not all endpoints require encryption, and not all encrypted payloads return encrypted payloads, but we have to also write a decryption sequence for the returns that are encrypted. Also, never forget that they have three restaurants that share a customer database - OG, LH, CG"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [],
   "source": [
    "baseurl = \"https://services.darden.com/api/v1\"\n",
    "params = {'conceptCode': 'OG', 'locale': 'en_US'}\n",
    "url = baseurl + '/signin'\n",
    "session.headers.update({'Content-Type': 'application/xml'})\n",
    "res = session.post(url, params=params, data=xmlfile)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Decrypt return"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It is essentially just the same as encrypt but backwards"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\r\n",
      "  \"userId\": \"1133661560168\",\r\n",
      "  \"currentShoppingCartId\": \"112586694264\"\r\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "kmgr = xmlsec.KeysManager()\n",
    "privatekeybundle = \"olivegardenencryptmessage_prod_darden.p12\"\n",
    "decryptpass = \"Darden17\"\n",
    "kmgr.add_key(xmlsec.Key.from_file(privatekeybundle,\n",
    "                                  format=xmlsec.constants.KeyDataFormatPkcs12,\n",
    "                                  password=decryptpass))\n",
    "ctx = xmlsec.EncryptionContext(kmgr)\n",
    "ret = etree.fromstring(res.text)\n",
    "dec = ctx.decrypt(ret)\n",
    "todecode = etree.fromstring(dec).text\n",
    "output = base64.b64decode(todecode).decode('utf-8')\n",
    "print(output)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Grab userinfo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that we can send and receive with the api, we can grab the user info as well. It just requires the userid that was provided."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\r\n",
      "\t\"notificationId\" : \"\",\r\n",
      "\t\"userId\" : \"1133661560168\",\r\n",
      "\t\"superWineUser\":false,\r\n",
      "\t\"allowMobilePay\":true,\r\n",
      "\t\"userInfo\" : {\r\n",
      "    \"name\" : {\"salutation\" : \"\", \"firstname\" : \"Stephen\", \"lastname\" : \"Champion\",\"suffix\" : \"\"},\r\n",
      "    \"guestDesignations\":[],\r\n",
      " \"expressCheckoutSettings\": null,\r\n",
      "   \r\n",
      "\t\t\"addresses\" : [],\r\n",
      "\t\t\"phones\" : [{ \"phoneId\": \"1102986224\", \"extension\": null, \"phoneNumber\": \"(618) 541-5730\", \"type\": \"MOBILE\"}],\r\n",
      "\t\t\"email\" : \"stephen.champion27@gmail.com\",\r\n",
      "\t\t\"dateOfBirth\" : \"02/27/1989\",\r\n",
      "\t\t\"preferredRestaurant\" : [{\"preferredRestaurant\":{\"restaurantIdentifier\":\"700646\",\"siteId\":\"oliveGardenMobileSite\",\"creationDate\":{\"time\":1550188120622},\"isFavorite\":1},\"siteId\":\"oliveGardenMobileSite\"}],\r\n",
      "\t\t\"favoriteMenus\" : [],\r\n",
      "\t\t\"favoriteRestaurants\" : [{ \"id\": \"700646\", \"restaurantNum\": \"1767\", \"corporationNumber\": \"TOG\"}],\r\n",
      "\t\t\"subscriptionTopics\" : [],\r\n",
      "\t\t\"subscriptions\":[{\"lastEmailOptInChange\":1580947421456,\"textSignupSource\":null,\"textOptinStatus\":null,\"conceptCode\":\"CS\",\"emailOptinStatus\":\"P\",\"lastTextOptInChange\":null,\"emailSignupSource\":\"cs_msignup_header\"}],\r\n",
      "\t\t\"security\": {\r\n",
      "\t\t   \"question\": \"\",\r\n",
      "\t\t   \"questionName\":\"\",\r\n",
      "\t\t   \"answer\": \"\"\r\n",
      "\t\t},\r\n",
      "\t\t\"social\": true,\r\n",
      "\t\t\"Preferences\":{\"CulinaryPreferences1\":[{\"preferenceType\":\"preference\",\"preferenceOption\":[{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Fish & Seafood\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Lighter Italian Fare (Under 575 Calories)\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Classic Recipes (Like Chicken Parmigiana & Tour Of Italy)\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Appetizers & Small Plates\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Soups & Salads\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Calzones & Italian Sandwiches\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Pizzas\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Chicken Dishes\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Beef Dishes\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Pork Dishes\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Wine\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Vegetarian\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Low Fat\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Specialty Cocktails\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Gluten Free\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Beer\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Non-Alcoholic Beverages\",\"optionalData\":null}],\"preferenceName\":\"OG Culinary Preferences\",\"checked\":false}],\"Interests0\":[{\"preferenceType\":\"preference\",\"preferenceOption\":[{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Wine\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Recipes\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Contests\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"New Menu Items\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Good For Kids\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Good For Gifting\",\"optionalData\":null}],\"preferenceName\":\"OG Interests\",\"checked\":false}],\"Occasions2\":[{\"preferenceType\":\"preference\",\"preferenceOption\":[{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Casual Dinner\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Brunch On The Weekends\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Weekday Dinner\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Celebration- Birthday, Anniversary, Other\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Lunch With Coworkers\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Family Friends\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Work\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Togo\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Lunch Break From Work\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"At Home With a Meal To Go\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Business Entertaining\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Happy Hour\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Girls Night Out\",\"optionalData\":null},{\"editable\":false,\"checked\":false,\"preferenceOptionName\":\"Date Night\",\"optionalData\":null}],\"preferenceName\":\"OG Occasions\",\"checked\":false}]},\r\n",
      "\t\t\r\n",
      "\t\t\"defaultAddressId\" : 1133713237073,\r\n",
      "\t\t\"defaultPhoneId\" : 1102986224,\r\n",
      "\t\t\"defaultCreditCardPaymentToken\": null,\r\n",
      "\t\t\"sweepStakes\": null,\r\n",
      "\t\t\"sweepStakesList\": [],\r\n",
      "\t\t \"picture\":null,\r\n",
      "\t\t\"likedMomentId\":[],\r\n",
      "\t\t\"reportedMomentId\":[],\r\n",
      "\t\t\"momentIds\":[],\r\n",
      "\t\t\"giftCards\":[],\r\n",
      "\t\t\"pickupInfo\": {\"includeBread\":false,\"condimentInfo\":[],\"includeSilverware\":false},\r\n",
      "\t\t\"isGCLimitExceeded\":false\r\n",
      "\t}\r\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "outdict = json.loads(output)\n",
    "userid = outdict[\"userId\"]\n",
    "profileurl = baseurl + '/profile'\n",
    "session.headers.update({'Content-Type': 'application/json'})\n",
    "userpayload = {'userId': userid}\n",
    "encrypteduserdata = session.post(profileurl, params=params, json=userpayload)\n",
    "kmgr = xmlsec.KeysManager()\n",
    "privatekeybundle = \"olivegardenencryptmessage_prod_darden.p12\"\n",
    "decryptpass = \"Darden17\"\n",
    "kmgr.add_key(xmlsec.Key.from_file(privatekeybundle,\n",
    "                                  format=xmlsec.constants.KeyDataFormatPkcs12,\n",
    "                                  password=decryptpass))\n",
    "ctx = xmlsec.EncryptionContext(kmgr)\n",
    "ret = etree.fromstring(encrypteduserdata.text)\n",
    "dec = ctx.decrypt(ret)\n",
    "todecode = etree.fromstring(dec).text\n",
    "output = base64.b64decode(todecode).decode('utf-8')\n",
    "print(output)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}